using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
using HotFixFramework;
using LitJson;
using Borodar.RainbowFolders.Editor;
using Borodar.RainbowFolders.Editor.Settings;

namespace ClientExtensions
{
    public class BundleBuilderModel
    {
        public string FilePathByBundle;
        public string BundleName;
        public string MD5;
        public int Version;
        public string FilePathByAsset;
    }


    public class RainbowFolderExt
    {
        public RainbowFolder FolderInfo;
        public string FullPath;
    }

    public static class BundleBuilder
    {
        //生成bundle的路径
        private const string RootPath = "Bundles/";
        private const string EditorABRootPath = RootPath + "EditorAssetBundles";
        private const string AndroidABRootPath = RootPath + "AndroidAssetBundles";
        private const string IOSABRootPath = RootPath + "IOSAssetBundles";

        private const string NoneExtPlaceholder = "NoExt";
        public static void MarkBundle()
        {
            Selection.assetGUIDs.ToList().ForEach(
                assetGuid =>
                {
                    var assetPath = AssetDatabase.GUIDToAssetPath(assetGuid);
                    if (!AssetDatabase.IsValidFolder(assetPath) || !FileHelper.IsBundlePath(assetPath))
                    {
                        return;
                    }

                    RainbowFoldersContextMenu.SetBundleTag(assetPath, FolderTagName.Red);
                }
            );
        }

        public static void UnMarkBundle()
        {
            RainbowFoldersContextMenu.RevertToDefault();
        }

        public static bool BuildMiddlewareFile()
        {
            string tmp;
            BuildMiddlewareFile(null, out tmp);
            return true;
        }

        private static bool BuildMiddlewareFile(Dictionary<string, List<BundleBuilderModel>> bundleMaps, out string ext2file2pathText)
        {
            Dictionary<string, Dictionary<string, string>> ext2file2path = new Dictionary<string, Dictionary<string, string>>();
            ext2file2pathText = null;

            string bundlePathPrefix = "Assets/" + HotFixConfigs.BundleRootDir + "/";
            List<BundleBuilderModel> bundleBuilderData = new List<BundleBuilderModel>();
            HashSet<string> signedFolderChecker = new HashSet<string>();
            List<RainbowFolderExt> folders = new List<RainbowFolderExt>();
            foreach (var folder in RainbowFoldersSettings.Instance.Folders)
            {
                if (folder.Type == RainbowFolder.KeyType.Path)
                {
                    if (FileHelper.IsBundlePath(folder.Key))
                    {
                        Debug.Log(folder.Key);
                        string fullFolderPath = FileHelper.CombinePath(FileHelper.EditorProjectDir, folder.Key);
                        signedFolderChecker.Add(fullFolderPath);
                        folders.Add(new RainbowFolderExt()
                        {
                            FullPath = fullFolderPath,
                            FolderInfo = folder,
                        });
                    }
                }
            }


            //ext2file2path is a dictionary which key is file extension and value is file-filepath dictionary
            //this allows you to find a resource just use its name if file-name is distinct in all asset bundles
            foreach (var folder in folders)
            {
                List<string> files = new List<string>();
                List<string> paths = new List<string>();
                FileHelper.Recursive(folder.FullPath, files, paths);

                //check bundled directory not contains a bundled directory
                foreach (var path in paths)
                {
                    if (signedFolderChecker.Contains(path))
                    {
                        var relatePath = path.Substring(path.LastIndexOf("Assets/"));
                        EditorUtility.DisplayDialog("Error", "AssetBundle folder:[" + folder.FolderInfo.Key + "] 's child:[" + relatePath + "] has signed as a bundle", "ok");
                        return false;
                    }
                }

                var bundleName = SubPathPrefx(bundlePathPrefix, folder.FolderInfo.Key);
                foreach (var fileName in files)
                {
                    var filePathFromBundleRoot = SubPathPrefx(bundlePathPrefix, fileName);
                    var filePathFromProjectRoot = fileName.Substring(fileName.LastIndexOf("Assets/"));
                    var model = new BundleBuilderModel()
                    {
                        FilePathByBundle = filePathFromBundleRoot,
                        BundleName = FileHelper.ABPathToCombName(bundleName),
                        //MD5
                        MD5 = FileHelper.GetMD5(fileName),
                        FilePathByAsset = filePathFromProjectRoot,
                    };
                    bundleBuilderData.Add(model);

                    if (bundleMaps != null)
                    {
                        List<BundleBuilderModel> bundleItems;
                        if (bundleMaps.TryGetValue(model.BundleName, out bundleItems))
                        {
                            bundleItems.Add(model);
                        }
                        else
                        {
                            var items = new List<BundleBuilderModel>() { model };
                            bundleMaps.Add(model.BundleName, items);
                        }
                    }

                    if (ext2file2path != null)
                    {
                        var ext = FileHelper.GetExtensions(model.FilePathByBundle);
                        if (ext == "")
                        {
                            ext = NoneExtPlaceholder;
                        }

                        Dictionary<string, string> file2path;
                        if (!ext2file2path.TryGetValue(ext, out file2path))
                        {
                            file2path = new Dictionary<string, string>();
                            ext2file2path.Add(ext, file2path);
                        }

                        var name = GetFileName(model.FilePathByBundle, ext);
                        try
                        {
                            file2path.Add(name, model.FilePathByBundle);
                        }
                        catch (Exception e)
                        {
                            EditorUtility.DisplayDialog("Error", "you have enbale load by name, but resource  [" + name + "] is not distinct in all Assetbundles", "ok");
                            return false;
                        }
                    }
                }
            }

            string DataPath = HotFixConfigs.MiddleWarePath;
            string builderPath = FileHelper.CombinePath(FileHelper.EditorProjectDir, DataPath + "BundleBuilder.json");
            var data = JsonMapper.ToJson(bundleBuilderData);
            FileHelper.FileWriteAllText(builderPath, data.Replace("},", "},\n"));

            builderPath = FileHelper.CombinePath(FileHelper.EditorProjectDir, DataPath + HotFixConfigs.Ext2File2PathFile);
            ext2file2pathText = JsonMapper.ToJson(ext2file2path);
            FileHelper.FileWriteAllText(builderPath, ext2file2pathText.Replace("},", "},\n"));
            return true;
        }

        private static string SubPathPrefx(string pathPrefix, string path)
        {
            var index = path.LastIndexOf(pathPrefix);
            if (index == -1)
            {
                return path;
            }
            else
            {
                return path.Substring(index + pathPrefix.Length);
            }
        }

        public static void BuildAssetBundles(BuildTarget targetPlatform)
        {
            BuildAssetBundleOptions options;
            switch (HotFixConfigs.BundleCompressType)
            {
                case CompressType.None:
                    {
                        options = BuildAssetBundleOptions.UncompressedAssetBundle;
                        break;
                    }
                case CompressType.LZMA:
                    {
                        options = BuildAssetBundleOptions.None;
                        break;
                    }
                case CompressType.LZ4:
                    {
                        options = BuildAssetBundleOptions.ChunkBasedCompression;
                        break;
                    }
                default:
                    {
                        EditorUtility.DisplayDialog("Error", "unknow compress type, check out HotFixConfigs.cs", "ok");
                        return;
                    }
            }

            BuildAssetBundles(options | BuildAssetBundleOptions.DisableLoadAssetByFileName
                            | BuildAssetBundleOptions.DisableLoadAssetByFileNameWithExtension, targetPlatform);
        }

        private static void BuildAssetBundles(BuildAssetBundleOptions options, BuildTarget targetPlatform)
        {
            //ext2file2path is a dictionary which key is file extension and value is file-filepath dictionary
            //this allows you to find a resource just use its name if file-name is distinct in all asset bundles
            string ext2file2pathText = null;

            Dictionary<string, List<BundleBuilderModel>> buildMaps = new Dictionary<string, List<BundleBuilderModel>>();
            if (!BuildMiddlewareFile(buildMaps, out ext2file2pathText))
            {
                return;
            }

            string outputPath;
            if (targetPlatform == BuildTarget.Android)
            {
                outputPath = AndroidABRootPath;
            }
            else if (targetPlatform == BuildTarget.iOS)
            {
                outputPath = IOSABRootPath;
            }
            else
            {
                outputPath = EditorABRootPath;
            }

            var bundleOutputPath = FileHelper.CombinePath(outputPath, HotFixConfigs.BundleRootDir);
            List<AssetBundleBuild> maps = new List<AssetBundleBuild>();
            foreach (var item in buildMaps)
            {
                var map = new AssetBundleBuild();
                map.assetBundleName = item.Key;
                map.assetNames = item.Value.Select(i => i.FilePathByAsset).ToArray();
                map.addressableNames = item.Value.Select(i => i.FilePathByBundle).ToArray();
                maps.Add(map);
            }
            CreateOutputDirectory(bundleOutputPath);
            var manifest = BuildPipeline.BuildAssetBundles(bundleOutputPath, maps.ToArray(), options, targetPlatform);
            if (manifest == null)
            {
                return;
            }

            Debug.Log("building bundle-file-table and version file");
            Dictionary<string, string> bundleFileTable = new Dictionary<string, string>();
            List<ResVersion> versions = new List<ResVersion>();
            var absoluteBundlePath = FileHelper.CombinePath(FileHelper.EditorProjectDir, bundleOutputPath);

            //add main bundle that contains dependencies
            var mainBundleName = SubPathPrefx("/", HotFixConfigs.BundleRootDir);
            var mainBundlePath = FileHelper.CombinePath(absoluteBundlePath, mainBundleName);
            versions.Add(new ResVersion()
            {
                Name = mainBundleName,
                MD5 = FileHelper.GetMD5(mainBundlePath),
                Size = (int)FileHelper.GetFileSize(mainBundlePath),
            });
            foreach (var item in buildMaps)
            {
                var itemFullPath = FileHelper.CombinePath(absoluteBundlePath, item.Key);
                var version = new ResVersion();
                version.Name = item.Key;
                version.MD5 = FileHelper.GetMD5(itemFullPath);
                version.Size = (int)FileHelper.GetFileSize(itemFullPath);
                versions.Add(version);

                foreach (var file in item.Value)
                {
                    bundleFileTable.Add(file.FilePathByBundle, item.Key);
                }
            }

            var bundleFileTableText = JsonMapper.ToJson(bundleFileTable);
            WriteFileToOutput(outputPath, HotFixConfigs.BundleFileTable, bundleFileTableText);

            var resVersionText = JsonMapper.ToJson(versions);
            WriteFileToOutput(outputPath, HotFixConfigs.ResVersionFile, resVersionText);

            var versionText = (PlayerPrefs.GetInt(HotFixConfigs.KeyVersion, 0) + 1).ToString();
            WriteFileToOutput(outputPath, HotFixConfigs.VersionFile, versionText);

            WriteFileToOutput(outputPath, HotFixConfigs.Ext2File2PathFile, ext2file2pathText);
        }

        private static void WriteFileToOutput(string outputPath, string fileName, string data)
        {
            var outputFilePath = FileHelper.CombinePath(outputPath, fileName);
            FileHelper.FileWriteAllText(FileHelper.CombinePath(FileHelper.EditorProjectDir, outputFilePath), data);
        }

        private static void CreateOutputDirectory(string pathByAsset)
        {
            string fullPath = FileHelper.CombinePath(FileHelper.EditorProjectDir, pathByAsset);
            FileHelper.CreateDirectory(pathByAsset);
        }

        private static string GetFileName(string fileName, string ext)
        {
            var name = fileName.Substring(fileName.LastIndexOf('/') + 1);
            //如果希望加载不带后缀名，则放开此注释
            //实际应用一般往配置文件配置资源，
            //并且Resload可以设计为根据扩展名自行查找资源，因此建议使用扩展名
            //if (ext != NoneExtPlaceholder)
            //{
            //    return name.Substring(0, name.Length - ext.Length);
            //}

            return name;
        }
    }
}